package com.sky.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.mybatis.mp.core.mybatis.mapper.context.Pager;
import cn.mybatis.mp.core.sql.executor.chain.QueryChain;
import cn.mybatis.mp.core.sql.executor.chain.UpdateChain;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.*;
import com.sky.dto.*;
import com.sky.entity.Users;
import com.sky.entity.UserBans;
import com.sky.exception.BaseException;
import com.sky.mapper.UsersMapper;
import com.sky.mapper.UserBansMapper;
import com.sky.properties.CreatingUserEmailProperties;
import com.sky.properties.JwtProperties;
import com.sky.service.UserService;
import com.sky.service.UserBanService;
import com.sky.utils.*;
import com.sky.vo.UserPageVO;
import com.sky.vo.UserVO;
import com.sky.vo.UserProfileVO;
import db.sql.api.impl.cmd.basic.OrderByDirection;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;

/**
 * 用户服务实现类
 * 提供用户管理相关的核心功能，包括：
 * - 用户注册和邮箱验证
 * - 用户登录和认证
 * - 用户状态管理（封禁/解封）
 * - 用户信息查询和管理
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    private UsersMapper usersMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private CreatingUserEmailProperties creatingUserEmailProperties;

    @Autowired
    private EmailCreateAnAccountUtil emailCreateAnAccountUtil;

    @Autowired
    private EmailUtil emailUtil;

    @Autowired
    private JwtProperties jwtProperties;

    @Autowired
    private EncryptDecryptUtil encryptDecryptUtil;

    // Redis key前缀，用于用户封禁状态
    private static final String USER_BAN_KEY_PREFIX = "user:ban:";

    /**
     * 将用户封禁状态保存到Redis
     *
     * @param userId      用户ID
     * @param banDuration 封禁时长（毫秒）
     */
    private void saveUserBanStatusToRedis(Integer userId, long banDuration) {
        String key = USER_BAN_KEY_PREFIX + userId;
        redisTemplate.opsForValue().set(key, "1", banDuration, TimeUnit.MILLISECONDS);
    }

    /**
     * 从Redis中移除用户封禁状态
     *
     * @param userId 用户ID
     */
    private void removeUserBanStatusFromRedis(Integer userId) {
        String key = USER_BAN_KEY_PREFIX + userId;
        redisTemplate.delete(key);
    }

    /**
     * 检查用户是否在Redis中被标记为封禁状态
     *
     * @param userId 用户ID
     * @return 是否被封禁
     */
    private boolean isUserBannedInRedis(Integer userId) {
        String key = USER_BAN_KEY_PREFIX + userId;
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    /**
     * 处理用户注册时的邮箱验证
     * <p>
     * 流程：
     * 1. 验证用户输入数据的完整性
     * 2. 生成随机密钥用于加密
     * 3. 将用户信息加密并生成UUID
     * 4. 检查是否存在重复请求
     * 5. 将验证信息存入Redis（30分钟有效期）
     * 6. 生成验证链接
     * 7. 异步发送验证邮件
     *
     * @param creatingUserDTO 包含用户名、密码和邮箱的注册信息
     * @throws Exception 当验证过程出现问题时抛出异常
     */
    @Override
    public void creatingUserEmail(CreatingUserDTO creatingUserDTO) throws Exception {

        if (creatingUserDTO.getUsername().isEmpty() || creatingUserDTO.getPassword().isEmpty() || creatingUserDTO.getEmail().isEmpty()) {
            throw new BaseException(MessageConstant.LONGIN_EMPTY);
        }


        ObjectMapper objectMapper = new ObjectMapper(); // 创建JSON处理对象

        SecureRandom secureRandom = new SecureRandom();
        //生成随机数
        String password = String.valueOf(secureRandom.nextInt());


        // 将Map转换为JSON字符串
        String mapJson = objectMapper.writeValueAsString(creatingUserDTO);

        //为用户生成唯一的标识符
        String UUID = encryptDecryptUtil.encryptDataCBC(mapJson, password);

        log.info("UUID:{}", UUID);

        //查询用户是否短时间内重复发起请求
        String a = (String) redisTemplate.opsForValue().get(creatingUserDTO.getEmail());

        if (a != null) {
            throw new BaseException(MessageConstant.MAKING_REPEATED_REQUESTS);
        }

        //将集合以及用户邮箱存入Redis中
        redisTemplate.opsForValue().set(UUID, password);
        redisTemplate.opsForValue().set(creatingUserDTO.getEmail(), creatingUserDTO.getEmail());

        //设置有效期为30分钟
        redisTemplate.expire(UUID, 30, TimeUnit.MINUTES);
        redisTemplate.expire(creatingUserDTO.getEmail(), 30, TimeUnit.MINUTES);

        // 对 UUID 值进行 URL 编码
        String encodedUUID = URLEncoder.encode(UUID, StandardCharsets.UTF_8.toString());

        // 生成激活链接
        String userPath = creatingUserEmailProperties.getCreatingUserPATH() + "?uuid=" + encodedUUID;


        //创建邮件模版所需的map集合
        Map<String, Object> variables = new HashMap<>();
        variables.put("name", creatingUserDTO.getUsername());
        variables.put("verificationLink", userPath);

        //将数据导入email模版
        String email = emailCreateAnAccountUtil.renderTemplate(EmailTemplateConstant.CREATE_AN_ACCOUNT_TEMPLATE, variables);

        ExecutorService executor = Executors.newFixedThreadPool(10);
        try {
            executor.submit(() -> {
                try {
                    emailUtil.sendEmail(creatingUserDTO.getEmail(), EmailTemplateConstant.EMAIL_TEMPLATE_NAME, email);
                } catch (Exception e) {
                    // 添加日志记录
                    log.error("邮件发送失败", e);
                }
            });
        } finally {
            executor.shutdown();
        }


    }

    /**
     * 完成用户创建过程
     * <p>
     * 流程：
     * 1. 从Redis获取并验证UUID的有效性
     * 2. 解密用户信息
     * 3. 创建新用户记录
     * 4. 对密码进行加密处理
     * 5. 保存用户信息到数据库
     * 6. 清理Redis中的临时数据
     *
     * @param emailCreatingUserDTO 包含加密的用户信息
     * @throws Exception 当创建过程出现问题时抛出异常
     */
    @Override
    public void creatingUser(EmailCreatingUserDTO emailCreatingUserDTO) throws Exception {

        ObjectMapper objectMapper = new ObjectMapper(); // 创建JSON处理对象

        String UUID = emailCreatingUserDTO.getUserUUID();

        //从redis上面获取秘钥
        String password = (String) redisTemplate.opsForValue().get(UUID);

        if (password == null) {
            throw new BaseException(MessageConstant.VERIFY_EMAIL_EXPIRATION);
        }

        //解密
        String userInformation = encryptDecryptUtil.decryptDataCBC(UUID, password);

        Map<String, String> decryptedMap = objectMapper.readValue(userInformation, new TypeReference<Map<String, String>>() {
        });

        Users user = new Users();

        user.setUsername(decryptedMap.get("username"));
        //对明文密码进行加密存储
        user.setPassword(Md5Util.getMD5String(decryptedMap.get("password")));
        user.setEmail(decryptedMap.get("email"));
        user.setRegistrationDate(new Date());

        usersMapper.save(user);

        //删除redis用户记录释放内存
        redisTemplate.delete(UUID);
        redisTemplate.delete(decryptedMap.get("email"));

    }

    /**
     * 处理用户登录请求
     * <p>
     * 流程：
     * 1. 验证登录信息完整性
     * 2. 查询并验证用户信息
     * 3. 验证密码正确性
     * 4. 生成并存储登录令牌
     * 5. 更新最后登录时间
     * <p>
     * 安全特性：
     * - 使用MD5加密验证密码
     * - 生成JWT令牌
     * - 将令牌存储在Redis中
     *
     * @param loginDTO 包含用户名和密码的登录信息
     * @return 登录成功的token
     * @throws BaseException 当登录失败时抛出相应的异常
     */
    @Override
    public String userLogin(LoginDTO loginDTO) {

        //用户名，密码为空抛出异常
        if (loginDTO.getUsername().isEmpty() || loginDTO.getPassword().isEmpty()) {
            throw new BaseException(MessageConstant.LONGIN_Empty);
        }

        //从数据库查询用户数据
        Users user = usersMapper.get(where -> {
            where.eq(Users::getUsername, loginDTO.getUsername());
        });

        //查询用户数据
        if (user == null) {
            //没有用户数据，说明用户未注册抛出异常
            throw new BaseException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        if (!user.getPassword().equals(Md5Util.getMD5String(loginDTO.getPassword()))) {
            //密码不正确
            throw new BaseException(MessageConstant.PASSWORD_ERROR);
        }

        //检查账户是否被封禁
        if (user.getStatus().equals(1)) {
            throw new BaseException(MessageConstant.THE_ACCOUNT_HAS_BEEN_SUSPENDED);
        }

        //权限的登录
        StpUtil.login(user.getUserId());

        Map<String, Object> variables = new HashMap<>();
        variables.put(JwtClaimsConstant.USER_ID, user.getUserId());

        //生成token
        String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), variables);

        //将token存进redis
        redisTemplate.opsForValue().set(token, token);
        //设置存储时间
        redisTemplate.expire(token, jwtProperties.getUserTtl(), TimeUnit.MILLISECONDS);

        //更新用户最后登录时间
        user.setLastLogin(new Date());

        //提交数据库
        usersMapper.update(user);

        return token;

    }

    /**
     * 分页查询用户列表
     * <p>
     * 功能：
     * 1. 支持按用户名和邮箱模糊搜索
     * 2. 返回分页的用户信息
     * 3. 包含用户的基本信息和统计数据
     * <p>
     * 性能优化：
     * - 使用 QueryChain 进行高效查询
     * - 忽略空值条件
     * - 支持字符串修剪
     *
     * @param page     页码（从1开始）
     * @param pageSize 每页记录数
     * @param keyword  搜索关键词（可选）
     * @return 包含用户信息的分页对象
     */
    @Override
    public Pager<UserVO> getUserList(int page, int pageSize, String keyword) {
        // 1. 构建并执行分页查询
        QueryChain<Users> queryChain = QueryChain.of(usersMapper)
                // 设置查询优化选项
                .ignoreNullValueInCondition(true)
                .ignoreEmptyInCondition(true)
                .trimStringInCondition(true);

        // 如果有关键词，添加模糊查询条件
        if (StringUtils.hasText(keyword)) {
            queryChain.where(where -> where
                    .like(Users::getUsername, keyword)
                    .or()
                    .like(Users::getEmail, keyword)
            );
        }

        // 添加排序条件
        queryChain.orderBy(OrderByDirection.DESC, Users::getRegistrationDate);

        // 执行分页查询
        Pager<Users> usersPager = queryChain.paging(Pager.of(page, pageSize));

        // 2. 转换为 VO 对象
        List<UserVO> userVOList = usersPager.getResults().stream()
                .map(this::convertToUserVO)
                .collect(Collectors.toList());

        // 3. 构建返回结果
        Pager<UserVO> result = new Pager<>();
        result.setResults(userVOList);
        result.setTotal(usersPager.getTotal());
        result.setSize(usersPager.getSize());
        result.setNumber(usersPager.getNumber());

        return result;
    }

    /**
     * 检查并自动解除过期封禁
     * 使用Sa-Token进行状态管理，减少数据库查询
     */
    @Transactional
    public void checkAndUnbanExpiredUsers() {
        // 获取临时封禁用户列表
        List<String> bannedUserIds = redisTemplate.opsForList().range(RedisKeyConstant.TEMP_BANNED_USERS_LIST, 0, -1);
        log.info("获取临时封禁用户列表:{}", bannedUserIds);

        if (bannedUserIds == null || bannedUserIds.isEmpty()) {
            return;
        }

        for (String userIdStr : bannedUserIds) {
            Integer userId = Integer.valueOf(userIdStr);

            // 使用Sa-Token检查是否仍处于封禁状态
            if (!StpUtil.isDisable(userId)) {
                log.info("用户 {} 的封禁已过期，自动解除封禁状态", userId);

                // 更新用户状态为正常
                UpdateChain.of(usersMapper)
                        .update(Users.class)
                        .set(Users::getStatus, UserStatusConstant.NORMAL)
                        .eq(Users::getUserId, userId)
                        .execute();

                // 从临时封禁列表中移除
                redisTemplate.opsForList().remove(RedisKeyConstant.TEMP_BANNED_USERS_LIST, 0, userIdStr);
            }
        }
    }

    /**
     * 获取用户详细信息
     * <p>
     * 功能：
     * 1. 获取用户基本信息
     * 2. 获取用户统计数据
     * 3. 转换为前端所需的格式
     *
     * @param userId 用户ID
     * @return 用户详细信息VO对象
     * @throws BaseException 当用户不存在时抛出
     */
    @Override
    public UserVO getUserDetails(Integer userId) {
        Users user = usersMapper.get(where -> where.eq(Users::getUserId, userId));
        if (user == null) {
            throw new BaseException(MessageConstant.USER_NOT_FOUND);
        }
        return convertToUserVO(user);
    }

    /**
     * 重置用户密码
     * <p>
     * 功能：
     * 1. 将用户密码重置为默认密码
     * 2. 使用BCrypt进行密码加密
     * <p>
     * 安全措施：
     * - 验证用户存在性
     * - 使用事务确保操作原子性
     * - 使用安全的密码加密算法
     *
     * @param userId 用户ID
     * @return 新的默认密码
     * @throws BaseException 当用户不存在时抛出
     */
    @Override
    @Transactional
    public String resetUserPassword(Integer userId) {
        // 1. 检查用户是否存在
        Users user = usersMapper.get(where -> where.eq(Users::getUserId, userId));
        if (user == null) {
            throw new BaseException(MessageConstant.USER_NOT_FOUND);
        }

        // 2. 重置密码为默认密码
        String newPassword = UserStatusConstant.DEFAULT_PASSWORD;
        user.setPassword(BCrypt.hashpw(newPassword));
        usersMapper.update(user);

        return newPassword;
    }

    /**
     * 删除用户
     * <p>
     * 功能：
     * 1. 从系统中完全删除用户
     * 2. 清理用户会话
     * <p>
     * 安全措施：
     * - 验证用户存在性
     * - 使用事务确保操作原子性
     * - 强制用户下线
     * <p>
     * 注意：此操作不可逆，请谨慎使用
     *
     * @param userId 用户ID
     * @throws BaseException 当用户不存在时抛出
     */
    @Override
    @Transactional
    public void deleteUser(Integer userId) {
        // 1. 检查用户是否存在
        Users user = usersMapper.get(where -> where.eq(Users::getUserId, userId));
        if (user == null) {
            throw new BaseException(MessageConstant.USER_NOT_FOUND);
        }

        // 2. 删除用户
        usersMapper.delete(where -> where.eq(Users::getUserId, userId));

        // 3. 强制用户下线
        StpUtil.kickout(userId);
    }

    /**
     * 将用户实体转换为VO对象
     * <p>
     * 功能：
     * 1. 复制基本属性
     * 2. 转换数据格式
     * 3. 添加统计信息
     *
     * @param user 用户实体对象
     * @return 转换后的VO对象
     */
    private UserVO convertToUserVO(Users user) {
        UserVO vo = new UserVO();
        BeanUtils.copyProperties(user, vo);

        return vo;
    }

    /**
     * 修改用户密码
     * <p>
     * 功能：
     * 1. 验证旧密码是否正确
     * 2. 更新为新密码
     * <p>
     * 安全措施：
     * - 验证用户存在性
     * - 验证旧密码正确性
     * - 使用MD5加密存储新密码
     * - 使用事务确保操作原子性
     *
     * @param userId            用户ID
     * @param changePasswordDTO 包含旧密码和新密码的DTO
     * @throws BaseException 当用户不存在或旧密码错误时抛出
     */
    @Override
    @Transactional
    public void changePassword(Integer userId, ChangePasswordDTO changePasswordDTO) {
        // 1. 检查用户是否存在
        Users user = usersMapper.get(where -> where.eq(Users::getUserId, userId));
        if (user == null) {
            throw new BaseException(MessageConstant.USER_NOT_FOUND);
        }

        // 2. 验证旧密码是否正确
        String oldPasswordMd5 = Md5Util.getMD5String(changePasswordDTO.getOldPassword());
        if (!user.getPassword().equals(oldPasswordMd5)) {
            throw new BaseException(MessageConstant.PASSWORD_ERROR);
        }

        // 3. 更新新密码
        user.setPassword(Md5Util.getMD5String(changePasswordDTO.getNewPassword()));
        usersMapper.update(user);

        // 4. 可选：强制用户重新登录
        StpUtil.logout(userId);
    }

    /**
     * 检查用户名和邮箱是否匹配
     *
     * @param username 用户名
     * @param email    邮箱
     * @return 是否匹配
     */
    @Override
    public boolean checkUserEmailMatch(String username, String email) {
        Users user = usersMapper.get(where -> {
            where.eq(Users::getUsername, username)
                    .eq(Users::getEmail, email);
        });
        return user != null;
    }

    /**
     * 根据邮箱查询用户
     *
     * @param email 邮箱
     * @return 用户对象
     */
    @Override
    public Users getUserByEmail(String email) {
        return usersMapper.get(where -> where.eq(Users::getEmail, email));
    }

    /**
     * 通过邮箱重置密码
     *
     * @param email       邮箱
     * @param newPassword 新密码
     */
    @Override
    @Transactional
    public void resetPasswordByEmail(String email, String newPassword) {
        Users user = getUserByEmail(email);
        if (user == null) {
            throw new BaseException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        // 更新密码
        user.setPassword(Md5Util.getMD5String(newPassword));
        usersMapper.update(user);

        // 强制用户重新登录
        StpUtil.logout(user.getUserId());

    }

    /**
     * 获取用户分页列表
     *
     * @param page     页码
     * @param pageSize 每页大小
     * @param name     搜索关键词（用户名）
     * @return 用户分页数据(users)
     */
    @Override
    public Page<Users> getUsersPage(Integer page, Integer pageSize, String name) {
        // 构建查询链
        QueryChain<Users> queryChain = QueryChain.of(usersMapper)
                .ignoreNullValueInCondition(true)
                .ignoreEmptyInCondition(true)
                .trimStringInCondition(true);

        // 如果有用户名,添加模糊查询条件
        if (StringUtils.hasText(name)) {
            queryChain.like(Users::getUsername, name);
        }

        // 添加排序条件
        queryChain.orderBy(Users::getRegistrationDate);

        // 执行分页查询
        Pager<Users> pager = queryChain.paging(Pager.of(page, pageSize));

        // 转换为Page对象返回
        Page<Users> result = new Page<>(page, pageSize);
        result.setTotal(pager.getTotal());
        result.addAll(pager.getResults());

        return result;
    }

    /**
     * 用户退出登录
     *
     * @param token 用户token
     */
    @Override
    public void userLogout(String token) {
        // 如果token存在,从Redis中删除
        if (token != null) {
            redisTemplate.delete(token);
        }

        // 注销登录
        StpUtil.logout();

        log.info("用户退出登录成功");
    }

    /**
     * 获取用户个人信息
     *
     * @param userId 用户ID
     * @return 用户个人信息
     */
    @Override
    public UserProfileVO getUserProfile(Integer userId) {
        log.info("获取用户个人信息，用户ID：{}", userId);

        // 1. 从数据库查询用户信息
        Users user = usersMapper.get(where -> where.eq(Users::getUserId, userId));
        if (user == null) {
            throw new BaseException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        // 2. 转换为VO对象
        UserProfileVO userProfileVO = new UserProfileVO();
        userProfileVO.setUserId(user.getUserId().toString());
        userProfileVO.setUsername(user.getUsername());
        userProfileVO.setEmail(user.getEmail());

        return userProfileVO;
    }
}
